local skynet = require "skynet"
local socket = require "socket"
local crypt = require "crypt"
local CMD = require "center_proto"

local WATCHDOG

local client_fd
local client_addr = nil

local hall_fd = 0
local game_fd = 0
local game_ip = ""
local game_port = 0
local user_id = nil

local bReConnect = false
local oldTableId = 0
local keyindex = 1
local authed   = false
local descmp   = "123"
local deskey   = "123"
local timeout  = 0
local authsucceed = false
local lgameid   = nil       -- 后面注册时从游戏服带上这个ID

local randkey = {[1] = "dssferw1",[2] =  "34eghjhg",  [3] =  "34erwfgd", [4] = "2#433423",
                 [5] = "34521313",[6] =  "dfssffff",  [7] =  "erweerwe", [8] = "Fdfdfere",
                 [9] = "12342rew",[10] = "bdfdsere",  [11] = "evfgdrrt",[12] = "erwer331",
                 [13] ="223edfbf",[14] = "erwerfdf",  [15] = "45rte45$",[16] = "dfserdfe"}


local function local_clear()

    if game_fd > 0 then
        bReConnect = true
        socket.close(game_fd)
        skynet.error("1 close game ip: ")
        local index = 0
        while bReConnect == true do 
            skynet.sleep(10) 
            skynet.error("2 close game ip:"..index)
            if index > 10 then
                break;
            end
            index = index + 1
        end
        game_fd = 0
    end

    keyindex = 1
    authed   = false
    descmp   = "123"
    deskey   = "123"
    oldTableId = 0
    timeout  = 0
end

function string_split(s, sep)  
    local fields = {[1]="name",[2]="a",[3]="b"}  
    local sep = sep or ":"
    local pattern = "([^:]+)"  
    local index = 1
    string.gsub(s, pattern, function (c) 
                            fields[index] = c 
                            index = index + 1
                            end)  
    return fields[1],fields[2],fields[3]
end  

local function send_package(fd, pack)
	local package = string.pack(">s2", pack)
	socket.write(fd, package)
end

local function rquest_cent(cmd_, playid_, msg_) 
    local res = skynet.call(".centerdb", "lua", "REQUEST",{mcmd = CMD.Main_Center_module, cmd = cmd_, playid = playid_, msg = msg_})
    return res
end

local function rquest_regToCenter(msg)
   
    user_id,lgameid=string.unpack("I4I4", msg, 5)

    local res = rquest_cent(CMD.Sub_ModuleToCenter_Query,user_id) 
    skynet.error("user auth succeed userid:",user_id,"table_id: ",res.table_id," game_id:",lgameid,"is_match:",res.is_match)

    skynet.send(".centerdb", "lua", "RegiterToCent",user_id, skynet.self())
    
    local desencodestr = crypt.desencode(deskey,string.pack("<I4<I4<I4",user_id,res.table_id,res.is_match))  
    send_package(client_fd, string.pack(">I2>I2c"..string.len(desencodestr), CMD.Main_ClientAndGameSvr_UserModle, CMD.Sub_GateToClient_PreTableID, desencodestr))             
    
end

local function recv_pack(msg, sz)
    if sz >= 4 then
        local mcmd,scmd = string.unpack(">I2>I2", msg, 1) 
       
        skynet.error("w <- mcmd="..mcmd.." scmd="..scmd.." sz="..sz)
        if mcmd ==CMD.Main_ClientToSvr_NetGate and scmd==CMD.Sub_ClientToGate_LoginSuccess then
           authsucceed = true
           rquest_regToCenter(msg)
        else 
            local desencodestr = crypt.desencode(deskey,string.unpack("c"..(sz-4), msg, 5))         
            send_package(client_fd, string.pack(">I2>I2c"..string.len(desencodestr), mcmd, scmd, desencodestr))
        end
    end
end   

local function error_toClose()
    skynet.call(WATCHDOG, "lua", "close", client_fd)
end

local function send_gameDetectSocket(fd)
   send_package(fd, string.pack(">I2>I2", 0, 1))
end

local function send_gameGateInfor(fd)
    send_package(fd, string.pack(">I2>I2z", 300, 1013, client_addr))
end

local function init_checkauth(ip, port)
    skynet.error("start init_checkauth ")
    if authed == false then
        skynet.fork(function()
            while true do
                if  authed  then
                    skynet.error("find authed ok")
                    break
                end
                if timeout > 5 then
                    skynet.error("authz time out, to close fd")
                    error_toClose()
                    break
                end
                skynet.sleep(100)
                timeout = timeout + 1
            end
        end)
    end
end

local function connect_game(ip, port)
    game_fd = socket.open(ip, port)
    if not game_fd  then 
        skynet.error("game server cnn fail  ip="..ip.." port="..port )
        return false
    end
    game_ip = ip; game_port = port
    send_gameGateInfor(game_fd)
    skynet.error("connect_game ip="..ip.." port="..port.." fd="..game_fd)
    if game_fd > 0 then
        skynet.fork(function()
            while true do
                local len = 0
                local read_size = socket.read(game_fd, 2)
                if not read_size  then
                    if bReConnect then
                        skynet.error("reconnect other game")
                        bReConnect = false
                        break
                    end
                    -- net disconnect
                    skynet.error("1 connect_game fd closed, try connect to:"..game_ip.." "..game_port )
                    socket.close(game_fd)
                    game_fd = 0
                    skynet.error("2 connect_game fd closed, try connect to:"..game_ip.." "..game_port )
                    skynet.call(WATCHDOG, "lua", "close", client_fd)

                    break
                else
                    len = string.unpack(">I2", read_size)
                    --skynet.error("read_size="..len)
                end
                if len > 0 then
                    local r = socket.read(game_fd, len)
                    if not r then
                        skynet.error("find game fd closed")
                        break
                    else
                        recv_pack(r, len)
                    end
                end  
            end
        end)
        return true
    end
    return false
end


local function bin2hex(s)
    s=string.gsub(s,"(.)",function (x) return string.format("%02X ",string.byte(x)) end)
    return s
end

local function out(msg) 
    skynet.error(bin2hex(msg))
end

local function re_handfirst()
    local clientkey = crypt.randomkey()

    keyindex = math.random(1, 16)

    local randstr = string.pack("c8c8",clientkey,randkey[keyindex])
    local shakey = crypt.hmac_sha1(clientkey, randstr)
   
    out(clientkey)
    out(randstr)
    out(shakey)

    deskey = string.sub(shakey, 1, 8)

    local dststr = string.pack("c8c8c8c8",clientkey,clientkey,clientkey,clientkey)
  
    descmp = crypt.desencode(deskey, dststr)

    out(deskey)
    out(dststr)

    skynet.error("keyindex="..keyindex);

    out(descmp)

    local ret = string.pack("<I2c8", keyindex, clientkey) 

    return ret
end

local function re_auth(msg,sz)
    if sz < (32 + 2) then
        return false
    end

    local authstr = string.unpack("c40",msg,5)
    if authstr ~= descmp then
        skynet.error("re_auth fail");
        out(authstr)
        out(descmp)
        return false
    end 

    skynet.error("re_auth ok");
    return true
end

local function gameReConnect(ip, port)
    if game_fd > 0 then
        bReConnect = true
        socket.close(game_fd)
        skynet.error("1 close game ip:"..ip.." port:"..port)
        local trynum = 0
        while bReConnect == true do 
            skynet.sleep(10) 
            skynet.error("2 close game ip:"..ip.." port:"..port)
            trynum = trynum + 1
            if trynum > 10 then 
                break
            end
        end
    end
    return connect_game(ip, port)
end

local function getIpPortByTable(tableIdPre)
    local recvtable = skynet.call(".configdb", "lua", "GET", tableIdPre)
    if recvtable == nil then
        skynet.error("getIpPortByTable not find tableIdPre :"..tableIdPre)
        return nil,nil
    end
    skynet.error("getIpPortByTable "..recvtable[1].." "..recvtable[2])
    return recvtable[1], recvtable[2]
end

local function getLinkNum()
    local count = skynet.call(WATCHDOG, "lua", "getlinknum")
    return tostring(count).."  "
end

local function recv_connectGame(msg)

    local tableIdPre =  string.unpack("<I2", msg, 5)

    skynet.error("Pre connectGame tabelPre"..tableIdPre);

    local ip, port = getIpPortByTable(tableIdPre)
    if ip == nil then
        return false
    end

    skynet.error("ip="..ip.." port="..port)

    if oldTableId == 0 or tableIdPre ~= oldTableId then
        goldTableId = tablePre
        return gameReConnect( ip, port)
    end
    
    return false
end

local function recv_getNetInfor(msg)
    return getLinkNum()
end

local function request(smsg, sz)
    if sz < 4 then
        skynet.error("close fd,find error packet sz:"..sz)
        error_toClose()
        return nil
    end

    local mcmd,scmd = string.unpack(">I2>I2", smsg, 1) 
    if mcmd > CMD.Main_Max or scmd > CMD.sub_Max then
        skynet.error("close fd: ->w mcmd="..mcmd.." scmd="..scmd.." sz="..sz)
        error_toClose()
        return nil
    end

    if mcmd ~= CMD.Main_ClientToSvr_NetGate  or scmd ~= CMD.Sub_ClientToGate_CheckNet then
        skynet.error("->w mcmd="..mcmd.." scmd="..scmd.." sz="..sz)
    end

    if mcmd == CMD.Main_ClientToSvr_NetGate then 
        if scmd == CMD.Sub_ClientToGate_Hand  and not authed then
            return string.pack(">I2>I2c10", CMD.Main_SvrToClient_NetGate, CMD.Sub_GateToClient_Hand , re_handfirst())
        end

        if scmd == CMD.Sub_ClientToGate_Auth and not authed then
            if not re_auth(smsg, sz) then
                return string.pack(">I2>I2<I2", CMD.Main_SvrToClient_NetGate, CMD.Sub_GateToClient_Auth,10)
            end
            authed = true    -- auth ok
            return string.pack(">I2>I2<I2",CMD.Main_SvrToClient_NetGate, CMD.Sub_GateToClient_Auth,20)
        end
        if scmd == CMD.Sub_ClientToGate_ConnectGameSer then
            if not recv_connectGame(smsg) then
                return string.pack(">I2>I2<I2",CMD.Main_SvrToClient_NetGate, CMD.Sub_GateToClient_ConnectGameSer,10)
            end
            return string.pack(">I2>I2<I2", CMD.Main_SvrToClient_NetGate, CMD.Sub_GateToClient_ConnectGameSer,20)
        end
        if scmd == CMD.Sub_ClientToGate_CheckNet then
            local sendIndex =  string.unpack("<I4", smsg, 5)
            return string.pack(">I2>I2<I4", CMD.Main_SvrToClient_NetGate, CMD.Sub_GateToClient_CheckNet ,sendIndex)
        end
        if scmd == CMD.Sub_ClientToGate_GetNetInfor then
            if authed == false then
                return
            end
            local str = recv_getNetInfor(smsg)
            local format = ">I2>I2c"..string.len(str)
            return string.pack(format, CMD.Main_SvrToClient_NetGate, CMD.Sub_ClientToGate_GetNetInfor, str)
        end

    elseif  authsucceed and (mcmd >= CMD.Main_Center_module  and  mcmd <= CMD.Main_Douhua_module ) then 
        local desdecodestr,result
        if sz > 4 then
            desdecodestr = crypt.desdecode(deskey,string.unpack("c"..(sz-4), smsg, 5))
        end
            
        result = skynet.call(".centerdb", "lua", "REQUEST",{mcmd = mcmd, cmd = scmd, playid = user_id, msg = desdecodestr})

        if type(result)=="table" then
            if result.msg then 
                local desencodestr = crypt.desencode(deskey,result.msg)
                return  string.pack(">I2>I2c"..string.len(desencodestr), result.mcmd, result.scmd, desencodestr)  
            else
                return  string.pack(">I2>I2", result.mcmd, result.scmd) 
            end
        end
    else 
        if game_fd > 0 then
            if sz > 4 then
                local desdecodestr = crypt.desdecode(deskey,string.unpack("c"..(sz-4), smsg, 5))
                send_package(game_fd, string.pack(">I2>I2c"..(string.len(desdecodestr)), mcmd, scmd, desdecodestr))
            else
                send_package(game_fd, string.pack(">I2>I2", mcmd, scmd))
            end
        end
    end
    
    -- not ok pack
    if authed == false and mcmd ~= 0 then
        skynet.error("find authed = false, recv packet : ->w mcmd="..mcmd.." scmd="..scmd.." sz="..sz)
        return nil
    end

    return nil
end


skynet.register_protocol {
	name = "client",
	id = skynet.PTYPE_CLIENT,
    unpack = function(msg,sz) 
        return  skynet.tostring(msg,sz), sz  
    end,
	dispatch = function (_, _, smsg, sz)
		local ok, result  = pcall(request, smsg, sz)
		if ok then
			if result then
				send_package(client_fd, result)
			end
		else
			skynet.error(result)
		end
	end
}

function CMD.start(conf)
	local fd = conf.client
	local gate = conf.gate
	WATCHDOG = conf.watchdog
    local s, e = string.find(conf.clientaddr, ":")
    client_addr = string.sub(conf.clientaddr, 1, s-1) 

    skynet.error("CMD.start ip="..client_addr)

	client_fd = fd
	skynet.call(gate, "lua", "forward", fd)

    --local_clear()
    init_checkauth()
end

function CMD.disconnect()
    if authsucceed then 
        rquest_cent(CMD.Sub_ModuleToCenter_UserDisn,user_id)--"set",user_id
        skynet.error("user_id: ",user_id," disconnect")
    end
    --local_clear()
    skynet.exit()
end

--来自.centerdb 的消息，并从中心服发过来的发送到客户端
function CMD.sendBoradcast(t)
    if authsucceed then 
        local gameid  = t.gameid
        local istable = t.istable

        --有游戏id,并不与本服的id 相同，则返回
        if gameid and tonumber(gameid) ~= tonumber(lgameid) then
            skynet.error("CMD.sendBoradcast no find gameid "..gameid.." lgameid "..lgameid)
            return
        end

        local mcmd = CMD.Main_hall_module
        local scmd = CMD.Sub_HallToModule_BoardTohall
        if istable then
            scmd = CMD.Sub_HallToModule_BoardTogame
        end

        local desencodestr = ""
        if t.msg then
            desencodestr = crypt.desencode(deskey, string.pack("<s2", t.msg))  

            skynet.error("CMD.sendBoradcast ",mcmd,scmd,t.msg)
            
            send_package(client_fd, string.pack(">I2>I2c"..string.len(desencodestr),mcmd, scmd, desencodestr))
        end
    end
end

--来自.centerdb 的消息，并从中心服发过来的发送到客户端
function CMD.sendPacket(t)
    local playid = t.playid
    local mcmd   = t.mcmd
    local scmd   = t.scmd
    local msg    = t.msg

    if authsucceed and playid == user_id then 
        local desencodestr = ""
        if msg then
            desencodestr = crypt.desencode(deskey,  msg)  
        end

        skynet.error("CMD.sendPacket",playid,mcmd,scmd)

        send_package(client_fd, string.pack(">I2>I2c"..string.len(desencodestr), mcmd, scmd, desencodestr))
    end
end

skynet.start(function()
    print("agent 1 start")
	skynet.dispatch("lua", function(_,_, command, ...)
		local f = CMD[command]
		skynet.ret(skynet.pack(f(...)))
	end)
    math.randomseed(os.time())
end)
